/*
 * TCSS 558
 * Distributed HashMap
 * Fall 2013
 * James Marquardt
 * Matt Adams
 * Justin Wong
 */
package registry;
import java.io.FileNotFoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Random;
import java.util.Set;

import model.HostPort;

import tools.Log;

/**
 * Implementation of the RemoteHashTable interface.
 * 
 * @author James Marquardt
 * @author Matt Adams
 * @author Justin Wong
 */
public class RemoteRegistryService implements RemoteRegistry {

	/**
	 * The m value of this system.
	 */
	private int m_value;
	
	/**
	 * Mappings of node_id to location.
	 */
	private HashMap<Integer, HostPort> node_mappings;
	
	/**
	 * The host and port of the last node that registered. This is used as an arbitrary node 
	 * reference for newly joined nodes.
	 */
	private HostPort lastHostPortRegistered = null;
	
	public static void start(final int port, final String registryName, final int mValue) {
	       try {
	            RemoteRegistryService obj = new RemoteRegistryService(mValue);
	            RemoteRegistry stub = 
	                    (RemoteRegistry) UnicastRemoteObject.exportObject(obj, 0);

	            // Bind the remote object's stub in the registry
	            LocateRegistry.createRegistry(port);
	            
	            Registry registry = LocateRegistry.getRegistry(port);
	            registry.bind(registryName, stub);

	            Log.out("Exported HashTable service as " + registryName + " on registry port" +
	                    " " + registryName);
	        } catch (Exception e) {
	            Log.err("Could not create registry: " + e);
	            System.exit(0);
	        }
	}	
	/**
	 * Constructor for RemoteHashTableService
	 * 
	 * @param the_m_value The m value for the system.
	 */
	public RemoteRegistryService(int the_m_value) {
		m_value = the_m_value;
		node_mappings = new HashMap<Integer, HostPort>();
		
	}
	
	@Override
	public int getArbitraryNode() {
		Set<Integer> myHashSet = node_mappings.keySet();
		
		int size = myHashSet.size();
		int item = new Random().nextInt(size);
		int i = 0;
		int k = 0;
		for(int j : myHashSet)
		{
		    if (i == item) {
		    	k = j;
		    	break;
		    }
		    i = i + 1;
		}
		return k;
	}
	
	@Override
	public RegistrationPacket registerNode(final HostPort the_hostPort) throws RemoteException, NodeLocationException {
		if (node_mappings.size() == (int) Math.pow(2, m_value)) {
			throw new IllegalArgumentException("Creating too many nodes for the chord. m value: " + m_value + " number of nodes:" + node_mappings.size());
		} else {
			int id = the_hostPort.hashCode()% (int) Math.pow(2, this.m_value);
			int i = 1;
			if( id < 0 ) {
			    id += (int) Math.pow(2, this.m_value);
			}
			while(node_mappings.containsKey(id)) {
				id = (id + i * i) % (int) Math.pow(2, this.m_value);
				i++;
			}
		
			//Cannot have two nodes at same location
			if(node_mappings.containsValue(the_hostPort)) {
				throw new NodeLocationException("Node already located at: " + the_hostPort);
			} else {
				System.out.println(id);
				node_mappings.put(id, the_hostPort);
				messageToFile("Adding node at " + the_hostPort + " to registry with id " + id);

				final RegistrationPacket packet = new RegistrationPacket(this.lastHostPortRegistered, this.m_value, id);
				this.lastHostPortRegistered = the_hostPort;

				return packet;
			}
		}
	}
	
    @Override
    public int getMValue() {
        return this.m_value;
    }
    
	@Override
	public HostPort getNodeHostPort(int the_node_id) throws RemoteException {
		return node_mappings.get(the_node_id);
	}
	
	@Override
	public void removeNode(int the_node_id) throws RemoteException{
		node_mappings.remove(the_node_id);
		messageToFile("Removed node " + the_node_id + " from registry.");
	}
	
	/**
     * Prints message to log file.
     * 
     * @param the_message
     */
    private void messageToFile(String the_message) {
    	try {
    	Log.toFile(the_message);
    	} catch (FileNotFoundException e) {
    		Log.err("could not create or find log file node registry");
    	}
    }
    
//  /**
//  * Main method for RemoteHashTableService
//  * @param args command line arguments
//  */
// public static void main(String[] args) {
//     if(args.length != 3) {
//         Log.err("Usage: java RemoteHashTableService <port> <registry-name> <m value>");
//         System.exit(0);
//     }
//     
//     try {
//           RemoteRegistryService obj = new RemoteRegistryService(Integer.parseInt(args[2]));
//           RemoteRegistry stub = 
//                 (RemoteRegistry) UnicastRemoteObject.exportObject(obj, 0);
//
//           // Bind the remote object's stub in the registry
//           LocateRegistry.createRegistry(Integer.parseInt(args[0]));
//           
//           Registry registry = LocateRegistry.getRegistry(Integer.parseInt(args[0]));
//           registry.bind(args[1], stub);
//
//           Log.out("Exported HashTable service as " + args[1] + " on registry port" +
//                 " " + args[0]);
//       } catch (Exception e) {
//           Log.err("Could not create registry: " + e);
//           System.exit(0);
//       }
//
// }
}
